/*
Description : Command line interface to control and monitor a relayduino board
			  (like kta223) thru its FTDI usb/serial adapter.
Author      : Samuel Ventura <samuel.ventura@yeico.com>
Copyright   : (c) 2011 Samuel Ventura. All rights reserved.
License 	: http://www.opensource.org/licenses/Apache-2.0
*/

using GLib;

namespace relayduino.cli {

	private Posix.termios oldin;
	private Kta223 kta;

    public void main(string[] args) {


		/* 14400  and 28800 are supported by the board but undefined on current vala posix vapi */
		var baudrates = new string[] {"1200", "2400", "4800", "9600", /*"14440",*/ "19200", /*"28800",*/ "38400", "57600", "115200"};

		var device_name = "/dev/ttyUSB0";
		var baudrate = "9600";
		var address = "0";
		var update_delay = "100";
		var show_comm = "false";
		var show_ai = "false";

		/* Parse arguments */
		for(int i=1;i<args.length;i++) {
			var arg = args[i];
			if ("-h" == arg) {
				stdout.printf("Relayduino CLI\n");
				stdout.printf("-h            Display this help\n");
				stdout.printf("-d {/dev/ttySX,/dev/ttyUSBX}\n"+
							  "              Select serial port device\n");
				stdout.printf("-b {1200,2400,4800,9600,19200,38400,57600,115200}\n"+
					   		  "              Select serial port baudrate\n");
				stdout.printf("-u millis     Poll delay in millis\n");
				stdout.printf("-s            Show communication log\n");
				stdout.printf("-ai           Show analog inputs\n");
				stdout.printf("-a            Device RS485 network address 0-99\n");
				exit(false, 0);
			} else if ("-s" == arg) {
				show_comm = "true";
			} else if ("-ai" == arg) {
				show_ai = "true";
			} else if ("-d" == arg) {
				if (i<args.length-1) device_name = args[++i];
				else {
					stdout.printf("Incomplete argument '%s'\n", arg);
					exit(false, -1);
				}
			} else if ("-b" == arg) {
				if (i<args.length-1) baudrate = args[++i];
				else {
					stdout.printf("Incomplete argument '%s'\n", arg);
					exit(false, -1);
				}
			} else if ("-u" == arg) {
				if (i<args.length-1) update_delay = args[++i];
				else {
					stdout.printf("Incomplete argument '%s'\n", arg);
					exit(false, -1);
				}
			} else if ("-a" == arg) {
				if (i<args.length-1) address = args[++i];
				else {
					stdout.printf("Incomplete argument '%s'\n", arg);
					exit(false, -1);
				}
			} else {
				stdout.printf("Invalid argument '%s'\n", arg);
				exit(false, -1);
			}
		}

		/* Check arguments */
		if (int.parse(update_delay)<=0) {
			stderr.printf("Invalid update delay '%s'\n", update_delay);
			exit(false, -1);
		}
		if (!FileUtils.test(device_name, FileTest.EXISTS)) {
			stderr.printf("Device not found '%s'\n", device_name);
			exit(false, -1);
		}
		var fd = Posix.open(device_name, Posix.O_RDWR | Posix.O_NOCTTY);
		if (fd < 0) {
			stderr.printf("Unable to open device '%s'\n", device_name);
			exit(false, -1);
		}
		if (!Posix.isatty(fd)) {
			stderr.printf("Device is not a tty '%s'\n", device_name);
			exit(false, -1);
		}
		if (!(baudrate in baudrates)) {
			stderr.printf("Invalid baudrate '%s'\n", baudrate);
			exit(false, -1);
		}
		if (int.parse(address)<=0 || int.parse(address)>99) {
			if (address != "0") {
				stderr.printf("Invalid address '%s'\n", address);
				exit(false, -1);
			}
		}

		/* setup serial port */
		kta = new Kta223.serial(fd, int.parse(baudrate), int.parse(address), bool.parse(show_comm));
        if (kta.setup_serial() < 0) {
			stderr.printf("Unable to setup kta\n");
			Posix.close(fd);
			exit(false, -1);
		}

		/* setup stdin to raw mode */
        if (Posix.tcgetattr(Posix.STDIN_FILENO, out oldin) < 0) {
			stderr.printf("Unable to get stdin termios\n");
			exit(false, -1);
		}
		var tio = oldin;
		tio.c_lflag &= ~(Posix.ICANON | Posix.ECHO);
        tio.c_cc[Posix.VMIN] = 0; /* immediate return if no data */
        tio.c_cc[Posix.VTIME] = 0;
		if (Posix.tcsetattr(Posix.STDIN_FILENO, Posix.TCSANOW, tio)<0) {
			stderr.printf("Unable to get stdin termios\n");
			exit(false, -1);
		}

    	stdout.printf("Relayduino CLI on %s at %s\n", device_name, baudrate);		
    	stdout.printf("Press '1'-'8' to toggle relays\n");
    	stdout.printf("Press 'q' to quit\n");

		var dinputs = 0;
		var doutputs = 0;
		var ainputs = new int[] {0, 0, 0, 0};

		while(true) {
			if (kta.read_all() < 0) {
				stderr.printf("Communication error\n");
				exit(true, -1);
			}

			if (bool.parse(show_ai)) {
				var dif_do = (doutputs != kta.doutputs);
				var dif_di = (dinputs != kta.dinputs);
				var dif_ai = (ainputs != kta.ainputs);

				if (dif_do || dif_di || dif_ai) {
					doutputs = kta.doutputs;
					dinputs = kta.dinputs;
					ainputs = kta.ainputs;
					stdout.printf("do:%d di:%d ai:%d,%d,%d\n", doutputs, dinputs, ainputs[0], ainputs[1], ainputs[2]);
				}
			} else {
				var dif_do = (doutputs != kta.doutputs);
				var dif_di = (dinputs != kta.dinputs);

				if (dif_do || dif_di) {
					doutputs = kta.doutputs;
					dinputs = kta.dinputs;
					ainputs = kta.ainputs;
					stdout.printf("do:%d di:%d\n", doutputs, dinputs);
				}
			}

			int ch = getch(int.parse(update_delay));
			if (ch < 0) continue;
			switch(ch) {
				case 'q':  exit(true, 0); break;
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8': 
					if (toggle(ch-'1') < 0) {
						stderr.printf("Communication error\n");
						exit(true, -1);
					}
					break;
			}
		}
    }

	private int exit(bool reset, int code) {
		if (reset) {
			kta.restore_serial();
			if (Posix.tcsetattr(Posix.STDIN_FILENO, Posix.TCSANOW, oldin) < 0) {
				stderr.printf("Unable to restore stdin termios\n");
			}
		}
		Posix.exit(code);
		return code;
	}

	private int toggle(int bit) {
		var doutputs = kta.doutputs;
		doutputs ^= (1 << bit);
		if (kta.write_doutputs(doutputs) < 0) return -1;
		return 0;
	}

	private int getch(int timeout) {
		var ts = Posix.timespec();
		Posix.clock_gettime(Posix.CLOCK_REALTIME, out ts);
		long cur = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
		long dl = cur + timeout;
		while(cur < dl) {
			int c = stdin.getc();
			if (c != -1) return c;
			Posix.usleep(10000); /* 10 millis */
			Posix.clock_gettime(Posix.CLOCK_REALTIME, out ts);
			cur = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
		}
		return -1;
	}
}
